
#include "dumper.h"

#include "disassembler.h"
#include "assembler.h"
#include "utility/list.h"
#include "utility/stringbuilder.h"
#include "os/os.h"

#ifndef ENABLE_DISASSEMBLER
#error disassembler disabled
#endif // ENABLE_DISASSEMBLER

namespace l8
{

void Dumper::Dump(FILE* f, byte* begin, byte* end)
{
    for (byte* pc = begin; pc < end; pc++)
    {
        fprintf(f, "%" PRINT_xPTR "  %4" PRINT_dPTR "  %02x\n",
                reinterpret_cast<uintptr_t>(pc), pc - begin, *pc);
    }
}


static void DumpBuffer(FILE* f, char* buff)
{
    ASSERT(f != NULL);
    fputs(buff, f);
}


// Limit for truncation in short printing.
static const int kMaxShortPrintLength = 1024;
static const int kOutBufferSize = 2048 + kMaxShortPrintLength;
static const int kRelocInfoPosition = 57;



static int DecodeIt(FILE* f,
                    const NameConverter& converter,
                    byte* begin,
                    byte* end)
{
    EmbeddedVector<char, 128> decode_buffer;
    EmbeddedVector<char, kOutBufferSize> out_buffer;

    byte* pc = begin;
    Disassembler d(converter);
    RelocIterator* it = NULL;

#if 0
    if (converter.code() != NULL)
    {
        it = new RelocIterator(converter.code());
    }
    else
    {
        // No relocation information when printing code stubs.
    }
#endif

    int constants = -1;  // no constants being decoded at the start

    while (pc < end) {
        // First decode instruction so that we know its length.
        byte* prev_pc = pc;
        if (constants > 0)
        {
            OS::SNPrintF(decode_buffer,
                         "%08x       constant",
                         *reinterpret_cast<int32_t*>(pc));
            constants--;
            pc += 4;
        }
        else
        {
            int num_const = d.ConstantPoolSizeAt(pc);
            if (num_const >= 0)
            {
                OS::SNPrintF(decode_buffer,
                             "%08x       constant pool begin",
                             *reinterpret_cast<int32_t*>(pc));
                constants = num_const;
                pc += 4;
            }
            else if (it != NULL && !it->done() && it->rinfo()->pc() == pc &&
                     it->rinfo()->rmode() == RelocInfo::INTERNAL_REFERENCE)
            {
                // raw pointer embedded in code stream, e.g., jump table
                byte* ptr = *reinterpret_cast<byte**>(pc);
                OS::SNPrintF(decode_buffer,
                             "%08" PRINT_xPTR "      jump table entry %4" PRINT_dPTR,
                             ptr,
                             ptr - begin);
                pc += 4;
            }
            else
            {
                decode_buffer[0] = '\0';
                pc += d.InstructionDecode(decode_buffer, pc);
            }
        }

        // Collect RelocInfo for this instruction (prev_pc .. pc-1)
        List<const char*> comments(4);
        List<byte*> pcs(1);
        List<RelocInfo::Mode> rmodes(1);
        List<intptr_t> datas(1);
        if (it != NULL) {
            while (!it->done() && it->rinfo()->pc() < pc) {
                if (RelocInfo::IsComment(it->rinfo()->rmode())) {
                    // For comments just collect the text.
                    comments.Add(reinterpret_cast<const char*>(it->rinfo()->data()));
                } else {
                    // For other reloc info collect all data.
                    pcs.Add(it->rinfo()->pc());
                    rmodes.Add(it->rinfo()->rmode());
                    datas.Add(it->rinfo()->data());
                }
                it->next();
            }
        }

        StringBuilder out(out_buffer.start(), out_buffer.length());

        // Comments.
        for (int i = 0; i < comments.length(); i++) {
            out.AddFormatted("                  %s\n", comments[i]);
        }

        // Write out comments, resets outp so that we can format the next line.
        DumpBuffer(f, out.Finalize());
        out.Reset();

        // Instruction address and instruction offset.
        out.AddFormatted("%p  %4d  ", prev_pc, prev_pc - begin);

        // Instruction.
        out.AddFormatted("%s", decode_buffer.start());

        // Print all the reloc info for this instruction which are not comments.
        for (int i = 0; i < pcs.length(); i++) {
            // Put together the reloc info
            RelocInfo relocinfo(pcs[i], rmodes[i], datas[i]);

            // Indent the printing of the reloc info.
            if (i == 0) {
                // The first reloc info is printed after the disassembled instruction.
                out.AddPadding(' ', kRelocInfoPosition - out.position());
            } else {
                // Additional reloc infos are printed on separate lines.
                out.AddFormatted("\n");
                out.AddPadding(' ', kRelocInfoPosition);
            }

            RelocInfo::Mode rmode = relocinfo.rmode();
            if (RelocInfo::IsPosition(rmode))
            {
                if (RelocInfo::IsStatementPosition(rmode))
                {
                    out.AddFormatted("    ;; debug: statement %d", relocinfo.data());
                }
                else
                {
                    out.AddFormatted("    ;; debug: position %d", relocinfo.data());
                }
            }
#if 0
            else if (rmode == RelocInfo::EMBEDDED_OBJECT)
            {
                HeapStringAllocator allocator;
                StringStream accumulator(&allocator);
                relocinfo.target_object()->ShortPrint(&accumulator);
                SmartPointer<const char> obj_name = accumulator.ToCString();
                out.AddFormatted("    ;; object: %s", *obj_name);
            }
            else if (rmode == RelocInfo::EXTERNAL_REFERENCE)
            {
                const char* reference_name = ref_encoder.NameOfAddress(*relocinfo.target_reference_address());
                out.AddFormatted("    ;; external reference (%s)", reference_name);
            }
            else if (RelocInfo::IsCodeTarget(rmode))
            {
                out.AddFormatted("    ;; code:");
                if (rmode == RelocInfo::CONSTRUCT_CALL) {
                    out.AddFormatted(" constructor,");
                }
                Code* code = Code::GetCodeFromTargetAddress(relocinfo.target_address());
                Code::Kind kind = code->kind();
                if (code->is_inline_cache_stub()) {
                    if (rmode == RelocInfo::CODE_TARGET_CONTEXT) {
                        out.AddFormatted(" contextual,");
                    }
                    InlineCacheState ic_state = code->ic_state();
                    out.AddFormatted(" %s, %s", Code::Kind2String(kind),
                                     Code::ICState2String(ic_state));
                    if (ic_state == MONOMORPHIC) {
                        PropertyType type = code->type();
                        out.AddFormatted(", %s", Code::PropertyType2String(type));
                    }
                    if (code->ic_in_loop() == IN_LOOP) {
                        out.AddFormatted(", in_loop");
                    }
                    if (kind == Code::CALL_IC) {
                        out.AddFormatted(", argc = %d", code->arguments_count());
                    }
                }
                else if (kind == Code::STUB)
                {
                    // Reverse lookup required as the minor key cannot be retrieved
                    // from the code object.
                    Object* obj = Heap::code_stubs()->SlowReverseLookup(code);
                    if (obj != Heap::undefined_value()) {
                        ASSERT(obj->IsSmi());
                        // Get the STUB key and extract major and minor key.
                        uint32_t key = Smi::cast(obj)->value();
                        uint32_t minor_key = CodeStub::MinorKeyFromKey(key);
                        ASSERT(code->major_key() == CodeStub::MajorKeyFromKey(key));
                        out.AddFormatted(" %s, %s, ",
                                         Code::Kind2String(kind),
                                         CodeStub::MajorName(code->major_key()));
                        switch (code->major_key()) {
                        case CodeStub::CallFunction:
                            out.AddFormatted("argc = %d", minor_key);
                            break;
                        case CodeStub::Runtime: {
                                const char* name =
                                        RuntimeStub::GetNameFromMinorKey(minor_key);
                                out.AddFormatted("%s", name);
                                break;
                            }
                        default:
                            out.AddFormatted("minor: %d", minor_key);
                        }
                    }
                }
                else
                {
                    out.AddFormatted(" %s", Code::Kind2String(kind));
                }
            }
#else
            else if (rmode == RelocInfo::EMBEDDED_OBJECT || rmode == RelocInfo::EXTERNAL_REFERENCE || RelocInfo::IsCodeTarget(rmode))
            {
                ASSERT(!"no impl");
            }
#endif
            else
            {
                out.AddFormatted("    ;; %s", RelocInfo::RelocModeName(rmode));
            }
        }
        out.AddString("\n");
        DumpBuffer(f, out.Finalize());
        out.Reset();
    }

    delete it;
    return pc - begin;
}



int Dumper::Decode(FILE* f, byte* begin, byte* end)
{
    NameConverter defaultConverter;
    return DecodeIt(f, defaultConverter, begin, end);
}


} // namespace l8
